package hevs.greencontrol.objects;

import java.util.ArrayList;
import java.util.List;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;

public class Room {

	// DATA
	private final int MARGE = 25;
	private Canvas canvas;
	private List<Point> points;
	private Paint paint;
	private RoomMode mode;
	private Point oldCenter;
	private Path path;
	
	// Mode of the room
	public enum RoomMode {CONSTRUCT_RECTANGLE, CONSTRUCT_CUSTOM, NORMAL, EDIT}
	
	
	// METHODS
	
	/**
	 * Constructor with room mode
	 */
	public Room(RoomMode mode)
	{
		this.mode = mode;
		
		this.canvas = new Canvas();
		
		points = new ArrayList<Point>();
	}
	
	/**
	 * Draw the room on the canvas in parameter
	 * @param canvas
	 */
	public void drawRoom(Canvas canvas)
	{
		this.canvas = canvas;
		
		switch (mode)
		{
		case CONSTRUCT_RECTANGLE:
			// Construct lines :
			drawConstructingLines();
			break;
			
		case CONSTRUCT_CUSTOM:
			// Construct lines :
			drawConstructingLines();
			break;

		case NORMAL:
			// Normal lines
			drawNormalLines();
			break;
			
		case EDIT:
			// Edit lines :
			drawEditingLines();
			break;

		default:
			// Normal lines
			drawNormalLines();
			break;
		}
	}
	
	/**
	 * Private method to draw the room with constructing lines
	 */
	private void drawConstructingLines()
	{
		// set the paint parameters
		paint = new Paint();
		paint.setColor(Color.GRAY);
		paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        
        // draw the walls
        drawWalls();
	}
	
	/**
	 * Private method to draw the room with normal lines
	 */
	private void drawNormalLines()
	{
		// set the paint parameters
		paint = new Paint();
		paint.setColor(Color.WHITE);
		paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        
        // draw the walls
        drawWalls();
	}
	
	/**
	 * Private method to draw the room with editing lines
	 */
	private void drawEditingLines()
	{
		// set the paint parameters
		paint = new Paint();
		paint.setColor(Color.WHITE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(4);
        paint.setPathEffect(new DashPathEffect(new float[] { 15, 5, 8, 5 }, 0) );
        
        // draw a circle in the middle
		Point middle = centroid();
		canvas.drawCircle(middle.getX(), middle.getY(), MARGE, paint);
		
		Paint cornerPaint = new Paint();
		cornerPaint.setColor(Color.RED);
		
		for(Point point : points)
		{
			canvas.drawCircle(point.getX(), point.getY(), MARGE, cornerPaint);
		}
		
		// draw the walls
		drawWalls();
	}
	
	/**
	 * Private method used to draw the walls of the room
	 */
    private void drawWalls()
    {
    	path = new Path();
        
		for (Point point : points)
		{
			if(points.indexOf(point) == 0)
			{
				path.moveTo(point.getX(), point.getY());
			}
			else
			{
				path.lineTo(point.getX(), point.getY());
			}
        }
        
		if(mode == RoomMode.CONSTRUCT_CUSTOM)
		{	
			canvas.drawCircle(points.get(0).getX(), points.get(0).getY(), MARGE, paint);
		}
		else
		{
			path.close();
		}
		
        canvas.drawPath(path, paint);
    }
    
    /**
     * Resize the room by matrix in parameter
     * @param matrix
     */
    public void resizeRoom(Matrix matrix)
    {
    	path.transform(matrix);
    }
    
	/**
	 * Set the first point of the rectangle with the point in parameter
	 * Create the others points with same coordinates
	 * @param topLeft
	 */
	public void setTopLeftRect(Point topLeft)
	{
		points.add(topLeft);
		points.add(new Point(topLeft));
		points.add(new Point(topLeft));
		points.add(new Point(topLeft));
	}
	
	/**
	 * Adjust the others points with the point in parameter and the first point
	 * @param bottomRight
	 */
	public void setBottomRightRect(Point bottomRight)
	{
		Point topLeft = points.get(0);
		
		points.get(1).setXY(bottomRight.getX(), topLeft.getY());
		points.get(2).setXY(bottomRight.getX(), bottomRight.getY());
		points.get(3).setXY(topLeft.getX(), bottomRight.getY());
	}
	
	/**
	 * Set the old center for moving the room
	 * @param oldCenter
	 */
	public void setOldCenter(Point oldCenter)
	{
		this.oldCenter = oldCenter;
	}
	
	/**
	 * Move the room the the new center
	 * @param newCenter
	 */
	public void moveToNewCenter(Point newCenter)
	{
		float moveX = newCenter.getX() - oldCenter.getX();
		float moveY = newCenter.getY() - oldCenter.getY();
		
		for(Point point : points)
		{
			point.movePoint(moveX, moveY);
		}
	}
	
	/**
	 * Add a point to the list
	 * @param point
	 */
	public void addPoint(Point point)
	{
		points.add(point);
	}
	
	/**
	 * Adjust last point
	 * @param point
	 */
	public void adjustLastPoint(Point point)
	{
		points.get(points.size() - 1).setXY(point.getX(), point.getY());
	}
	
	/**
	 * Move the corner index to the point in parameter
	 * @param index
	 * @param point
	 */
	public void moveCorner(int index, Point point)
	{
		points.get(index).setXY(point.getX(), point.getY());
	}
	
	/**
	 * Determine if point in parameter is near a corner of the room
	 * Return the index of the corner if it is close
	 * @param point
	 * @return clickedNearCorner
	 */
	public int clickedNearCorner(Point point)
	{
		for(Point corner : points)
		{
			if( point.getX() > corner.getX() - MARGE &&
				point.getX() < corner.getX() + MARGE &&
				point.getY() > corner.getY() - MARGE &&
				point.getY() < corner.getY() + MARGE)
				{
					return points.indexOf(corner);
				}
				
		}
		
		return -1;
	}
	
	/**
	 * Determine if point in parameter is near the center of the room
	 * Return true if it is close
	 * @param point
	 * @return clickedNearCenter
	 */
	public boolean clickedNearCenter(Point point)
	{
		Point center = centroid();
		
		if( point.getX() > center.getX() - MARGE &&
			point.getX() < center.getX() + MARGE &&
			point.getY() > center.getY() - MARGE &&
			point.getY() < center.getY() + MARGE)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Determine if point in parameter is near the first point of the room
	 * Return true if it is close
	 * @param point
	 * @return clickedNearOrigine
	 */
	public boolean clickedNearOrigine(Point point)
	{
		if( point.getX() > points.get(0).getX() - MARGE &&
			point.getX() < points.get(0).getX() + MARGE &&
			point.getY() > points.get(0).getY() - MARGE &&
			point.getY() < points.get(0).getY() + MARGE)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Delete the last point
	 */
	public void deleteLastPoint()
	{
		points.remove(points.size() - 1);
	}
	
	/**
	 * Get the center of the room
	 * @return center
	 */
    private Point centroid()
    {
        float cx = 0f;
        float cy = 0f;
        
		for (Point point : points)
		{
			Point nextPoint = new Point(0, 0);
			
			if(points.indexOf(point) == points.size()-1)
			{
				nextPoint = points.get(0);
			}
			else
			{
				nextPoint = points.get(points.indexOf(point) + 1);
			}
			
            cx = cx + (point.getX() + nextPoint.getX()) * (point.getY() * nextPoint.getX() - point.getX() * nextPoint.getY());
            cy = cy + (point.getY() + nextPoint.getY()) * (point.getY() * nextPoint.getX() - point.getX() * nextPoint.getY());
        }
        cx /= (6 * getArea());
        cy /= (6 * getArea());
        
        cx = Math.abs(cx);
        cy = Math.abs(cy);
        
        return new Point(cx, cy);
    }
    
    /**
     * Get the area of the room
     * @return area
     */
    public double getArea()
    {
    	double sum = 0.0;
    	
		for (Point point : points)
		{
			Point nextPoint = new Point(0, 0);
			
			if(points.indexOf(point) == points.size()-1)
			{
				nextPoint = points.get(0);
			}
			else
			{
				nextPoint = points.get(points.indexOf(point) + 1);
			}
			
            sum = sum + (point.getX() * nextPoint.getY()) - (point.getY() * nextPoint.getX());
        }
        
        return Math.abs(0.5 * sum);
	}

    
    /**
     * Determine if the point in parameter is in the room
     * Return true if the point is in
     * @param clickedPoint
     * @return clickedInRoom
     */
	public boolean clickedInRoom(Point clickedPoint)
	{
        int crossings = 0;
        
		for (Point point : points)
		{
			Point nextPoint = new Point(0, 0);
			
			if(points.indexOf(point) == points.size()-1)
			{
				nextPoint = points.get(0);
			}
			else
			{
				nextPoint = points.get(points.indexOf(point) + 1);
			}
			
            double slope = (nextPoint.getX() - point.getX())  / (nextPoint.getY() - point.getY());
            boolean cond1 = (point.getY() <= clickedPoint.getY()) && (clickedPoint.getY() < nextPoint.getY());
            boolean cond2 = (nextPoint.getY() <= clickedPoint.getY()) && (clickedPoint.getY() < point.getY());
            boolean cond3 = clickedPoint.getX() <  slope * (clickedPoint.getY() - point.getY()) + point.getX();
            
            if ((cond1 || cond2) && cond3) crossings++;
		}
        
        return (crossings % 2 != 0);
	}
	
	/**
	 * Switch between normal and edit mode
	 */
	public void switchMode()
	{
		switch (mode)
		{
		case NORMAL:
			mode = RoomMode.EDIT;
			break;
			
		case EDIT:
			mode = RoomMode.NORMAL;
			break;

		default:
			mode = RoomMode.NORMAL;
			break;
		}
	}

	
	// Getters / Setters
	
	/**
	 * Get the mode of the room
	 * @return mode
	 */
	public RoomMode getMode()
	{
		return mode;
	}

	/**
	 * Set the mode of the room
	 * @param mode
	 */
	public void setMode(RoomMode mode)
	{
		this.mode = mode;
	}
	
	/**
	 * Get a point of the list
	 * @param index
	 * @return point
	 */
	public Point getPoint(int index)
	{
		return points.get(index);
	}
	
	/**
	 * Get the number of points
	 * @return size
	 */
	public int getNumberPoints()
	{
		return points.size();
	}

	/**
	 * Get the list of points
	 * @return
	 */
	public List<Point> getPoints()
	{
		return points;
	}
	
	
}